package dproxy.server.net.backoff;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import dproxy.server.common.exceptions.AppInfrastructureException;
import dproxy.server.net.protocol.BaseProtocol;

/**
 * Waits for data to be available until timeout.
 */
public final class Timeout {

    /** Default retest times. */
    private static final int DEFAULT_RETEST_TIMES = 10;
    /** Default timeout. */
    private static final int DEFAULT_TIMEOUT = 5000;

    /** Singleton instance. */
    private static Timeout instance = new Timeout();

    /** Logger. */
    private Log logger = LogFactory.getLog(getClass());
    
    /** Interval to wait for more available data to read. */
    private final int readTimeout;
    /** Times to wait before @see Timeout#readTimeout is finished. */
    private final int readRetestTimes;
    /** Times to wait. */
    private Collection<Integer> intervals;
    
    /**
     * @return Singleton instance
     */
    public static Timeout getInstance() {
        return instance;
    }
    
    /**
     * Private constructor.
     */
    private Timeout() {
        // Default: Linear implementation, 5 seconds of timeout, 
        // retests at every half second
        int defaultTimeout = DEFAULT_TIMEOUT;
        int defaultRetestTimes = DEFAULT_RETEST_TIMES;
        String timeoutAlgorithm = IntervalGenerator.LINEAR;
        try {
            Properties networkingProperties = new Properties();
            InputStream netPropertiesStream = BaseProtocol
                .class.getResourceAsStream("/networking.properties");
            if (netPropertiesStream != null) {
                networkingProperties.load(netPropertiesStream);
                
                String readTimeout = networkingProperties
                    .getProperty("READ_TIMEOUT");
                try {
                    defaultTimeout = Integer.parseInt(readTimeout);
                } catch (NumberFormatException e) { 
                    // Use default 
                    if (logger.isDebugEnabled()) {
                        logger.debug("Using default timeout");
                    }
                }
                String retestTimes = networkingProperties
                    .getProperty("READ_RETEST_TIMES");
                try {
                    defaultRetestTimes = Integer.parseInt(retestTimes);
                } catch (NumberFormatException e) { 
                    // Use default
                    if (logger.isDebugEnabled()) {
                        logger.debug("Using default retest times");
                    }
                }
                
                String algorithmConfig = networkingProperties
                    .getProperty("BACKOFF_ALGORITHM");
                if (algorithmConfig != null) {
                    if (IntervalGenerator.LINEAR.equalsIgnoreCase(algorithmConfig)) {
                        timeoutAlgorithm = IntervalGenerator.LINEAR; 
                    } else if (IntervalGenerator.EXPONENTIAL.equalsIgnoreCase(algorithmConfig)) {
                        timeoutAlgorithm = IntervalGenerator.EXPONENTIAL;
                    } else {
                        // Use default
                        if (logger.isDebugEnabled()) {
                            logger.debug("Using default backoff algorithm");
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new AppInfrastructureException();
        }
        readTimeout = defaultTimeout;
        readRetestTimes = defaultRetestTimes;     
        intervals = IntervalGenerator.newInstance(timeoutAlgorithm).getIntervals(readTimeout, readRetestTimes);
    }
    
    /**
     * Waits for data to be available until timeout is reached.
     * @param inputStream stream to wait for data
     */
    public void waitForDataAvailable(InputStream inputStream) {
        try {
            Iterator<Integer> itIntervals = intervals.iterator();
            while (itIntervals.hasNext()) {
                int time = itIntervals.next();
                try { Thread.sleep(time); } catch (InterruptedException e) { 
                    if (logger.isDebugEnabled()) {
                        logger.debug("Caught interrupted exception waiting for data available", e);
                    }
                }
                if (inputStream.available() != 0) {
                    break;
                }
            }
        } catch (IOException e) {
            throw new AppInfrastructureException(e);
        }
    }

    /* Getters and setters */
    /** @return Returns the readTimeout */
    public int getReadTimeout() {
        return readTimeout;
    }
    
}